feat: arc path for layout and keyframe animations#3386
Conversation
|
closing PR while i work on it |
|
back at it 🫡 |
layout transitionslayout and keyframe animations
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
|
|
|
@mattgperry I've just been thinking, would this be more suitable as a cubic bezier instead of this I originally set this up to make it as easy as possible to enable curves but it does feel a little difficult to customise. Caveats are we'd lose the ability to have the "smart" checks that change logic based on context. Alternatively we could include both options with the
|
|
@mattgperry i know you're probably busy, but just curious if you've had any thoughts on this |
|
Hey @lochie! Sorry for the delay. Yeah I have some thoughts. I think it's looking good though. Maybe nice shorthands? I think bezier curve is getting close to offset path which we already basically support for normal animations - though I appreciate they don't work with layout animations. In terms of bundling I would like it to have minimal impact so I'm going to take a quick go at making it injectable also. |
|
@mattgperry all good! shorthand sounds like a good idea. |
|
Yeah hold off for now, I've got a branch I'm quite happy with, I'll get back to you with a PR to see if you're happy with it too |
motion-dom pays ~440-585 gzip bytes whether or not they use them.
This routes the same behavior through a generic Path factory:
users opt in via `import { arc }`, non-importers pay only the two
`if (path)` branches and the type plumbing (~125-175 bytes).
```ts
import { arc } from "motion/react"
<motion.div animate={{ x: 200 }} transition={{ path: arc() }} />
```
Bundle size, gzipped, vs main:
motion +176 B (was +585 B with hardcoded arc)
dom-animation +126 B (was +436 B)
dom-max +133 B (was +543 B)
animate +128 B (was +437 B)
The Path slot accepts `(from, to) => (t) => PathState` — same shape
fits future `bezier()`, `svg()`, etc.
Other deltas vs #3386:
- arc math primitives are now private; users consume via the factory.
- `orientToPath: true` maps to 1.0 (full intensity) instead of 0.5.
- `arc()` and `arc({})` both work — `amplitude` defaults to 0.5.
- Layout interruption continuity moves from a per-projection-node field
to closure state on the arc factory; reuse via module scope or
useMemo to keep it alive across renders.
- `isInterrupted` parameter dropped from Path — auto-direction handles
clean reversals naturally; the closure handles dominant-axis change.
- Cypress coverage expanded to keyframe arc + orientToPath rotation.
- Dev playground variants: ping-pong, axis-change, interrupt, cw, ccw.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>


Summary
Adds an
arctransition option that makes elements travel along a curved Bézier path rather than a straight line.Works with
motioncomponents,useAnimate, and layout animations vialayoutId:amplitude— how pronounced the arc is, as a fraction of total travel distancepeak— where along the path the arc reaches maximum height (default0.5= symmetric)direction—"cw"/"ccw"to fix which side the arc bulges toward; when unset, auto-picks a consistent screen-space direction regardless of travel directionorientToPath— rotates the element to follow the tangent of the arc path. higher number = more intensityOpinionated choices:
scale) are unaffectedTest plan
useAnimateexample toggles between positions along an arclayoutIdelement between positions — should travel in a curve, not a straight lineamplitudeslider —0= straight line,1= arc equal to travel distancepeak— arc should shift earlier/later along the pathdirection— arc should lock tocworccwregardless of movement direction